Experiment: does --hidden-import=gribapi bundle eccodes.dll for pygrib? - #415
Conversation
…or pygrib? Standalone, non-gating probe (does not invoke run_setup.bat) testing PRD Requirement 1's cheapest-option-first question: create a scratch conda env with pygrib+eccodes+python-eccodes+pyinstaller, build a trivial pygrib stub twice (control vs --hidden-import=gribapi), and record whether the existing pyinstaller-hooks-contrib hook-gribapi.py bundles eccodes.dll for free. Research (docs/prd-conda-native-dll-bundling.md Finding 1) suggests this is likely negative since pygrib and gribapi are independent bindings to the same C library, but the owner asked for real CI evidence before ruling it out. Wired as its own step in the conda-full lane, gated on the existing conda-availability check, continue-on-error. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A4iE1BRSkUETwz237XeuTW
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
Warning Review limit reached
Next review available in: 28 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
📝 WalkthroughSummary by CodeRabbit
WalkthroughChangesGRIB-API hook probe
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant CI as conda-full CI
participant Probe as PowerShell probe
participant Conda as conda environment
participant PyInstaller
participant EXE as built executable
participant NDJSON as NDJSON results
CI->>Probe: Invoke probe when Miniconda is available
Probe->>Conda: Create environment and sample app
Probe->>PyInstaller: Build control variant
PyInstaller-->>Probe: Return build output and executable
Probe->>EXE: Run control executable
Probe->>PyInstaller: Build variant with --hidden-import gribapi
PyInstaller-->>Probe: Return build output and executable
Probe->>EXE: Run experiment executable
Probe->>NDJSON: Record DLL, runtime, and conclusiveness results
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/batch-check.yml:
- Around line 997-1002: Update the NDJSON verdict and enforcement flow for
self.gribapi_hook_probe.hidden_import so its setup or inconclusive failures do
not fail the conda-full lane, while retaining the probe row in artifacts and the
Job Summary. Prefer excluding this specific probe from the gating verdict;
otherwise adjust its exploratory output to avoid pass=false without affecting
other probes.
In `@tests/selfapps_gribapi_hook_probe.ps1`:
- Line 109: Rename the temporary log filenames assigned near $envCreateLog and
the corresponding build log variable to use the required tilde prefix, such as
~env_create.log and ~build_$Variant.log, while preserving the existing paths and
logging behavior.
- Around line 189-216: Update the runtime observation logic around the EXE
execution block and the later `$conclusive` calculation so each probe records
whether the process launched, completed within the timeout, and had both output
streams captured. Set `$conclusive` only when both EXE observations satisfy all
completion and capture conditions; keep timed-out processes, launch exceptions,
and unreadable output inconclusive rather than deriving pass or
hiddenImportHelped results from them.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: dbe04c65-7f8f-4471-a75a-d38515cd22ce
📒 Files selected for processing (3)
.github/workflows/batch-check.ymldocs/agent-ndjson.mdtests/selfapps_gribapi_hook_probe.ps1
📜 Review details
⏰ Context from checks skipped due to timeout. (9)
- GitHub Check: auto_merge
- GitHub Check: Batch syntax/run check (contract-uv-fail)
- GitHub Check: Batch syntax/run check (uv)
- GitHub Check: Batch syntax/run check (cache)
- GitHub Check: Batch syntax/run check (real)
- GitHub Check: Batch syntax/run check (uv-dl-fallback)
- GitHub Check: Batch syntax/run check (conda-full)
- GitHub Check: Batch syntax/run check (justme-test)
- GitHub Check: Batch syntax/run check (contract-uv)
🧰 Additional context used
📓 Path-based instructions (12)
**/*
📄 CodeRabbit inference engine (AGENTS.md)
The single-bootstrapper directive requires
run_setup.batto work when dropped next to the application without committed helper files; test-only helpers may live undertests/but cannot be required by the real flow.
Files:
docs/agent-ndjson.mdtests/selfapps_gribapi_hook_probe.ps1
**/*.{md,txt}
📄 CodeRabbit inference engine (AGENTS.md)
When documenting
run_setup.bat, cite labels or subroutine names rather than exact line numbers unless a line number provides immediate value for the introducing commit.
Files:
docs/agent-ndjson.md
docs/agent-*.md
📄 CodeRabbit inference engine (CLAUDE.md)
When a change teaches or invalidates a lesson, update the relevant knowledge document in the same commit, editing existing entries rather than only appending. Move resolved Active Backlog items to docs/agent-closed-backlog.md.
Files:
docs/agent-ndjson.md
**/.github/workflows/*.{yml,yaml}
📄 CodeRabbit inference engine (AGENTS.md)
**/.github/workflows/*.{yml,yaml}: Use GitHub Actions CI results, especially Job Summary, grouped log tails, and workflow PR failure comments, as the source of truth; local runs are advisory.
Isolate slow, flaky, or environment-dependent diagnostics in separate non-gating lanes usingcontinue-on-errorrather than weakening deterministic gating lanes.
Keep CI parser checks synchronized with exact bootstrapper messages and preserve the single parser-facing iterate signal* Iterate logs: {found|missing}.
The iterate job must upload one artifact namediterate-logs-${run_id}-${run_attempt}containingiterate/_temp/and the job summary; do not alter the intentional pre-flight gate failure for missing NDJSON inputs.
Files:
.github/workflows/batch-check.yml
.github/workflows/*.yml
📄 CodeRabbit inference engine (CLAUDE.md)
Validate modified GitHub Actions workflows with both
actionlintand YAML syntax/lint checks before committing.
Files:
.github/workflows/batch-check.yml
**/*.{ps1,psm1,psd1}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ps1,psm1,psd1}: Prepend TLS 1.2 configuration to every PowerShell 5.1Invoke-WebRequestcall and retain-UseBasicParsing.
Validate touched PowerShell files with PowerShell AST parsing; do not skip validation on Linux, and run modified scripts directly with realistic environment variables when practical.
Files:
tests/selfapps_gribapi_hook_probe.ps1
**/*.{bat,cmd,ps1,py,yml,yaml,json}
📄 CodeRabbit inference engine (AGENTS.md)
Run delimiter and repository-specific syntax regression checks using
tools/check_delimiters.py, respecting comments, escapes, and here-strings as applicable.
Files:
tests/selfapps_gribapi_hook_probe.ps1
**/*.{py,ps1,psm1,psd1,yml,yaml,json,bat,cmd}
📄 CodeRabbit inference engine (AGENTS.md)
Keep source text ASCII plain text and avoid non-ASCII punctuation.
Files:
tests/selfapps_gribapi_hook_probe.ps1
**/*.{bat,ps1}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{bat,ps1}: Use ASCII-only content; avoid emojis, curly quotes, em-dashes, and other non-ASCII characters because Windows batch and PowerShell parsing can break.
Keep .bat and .ps1 files in CRLF format; other files use LF, as controlled by .gitattributes.
Avoid EnableDelayedExpansion in batch scripts; if required, scope it tightly.
Files:
tests/selfapps_gribapi_hook_probe.ps1
**/*.{bat,ps1,py}
📄 CodeRabbit inference engine (CLAUDE.md)
Use tilde-prefixed temporary files such as
~setup.logand~bootstrap.status.jsonso they are easy to ignore and survive crashes.
Files:
tests/selfapps_gribapi_hook_probe.ps1
**/*.ps1
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.ps1: Use PowerShell-orand-andonly in valid conditional expressions; outside conditionals they may be parsed as parameter names.
Run a PowerShell parser sweep over modified PowerShell scripts before committing.
Files:
tests/selfapps_gribapi_hook_probe.ps1
tests/selfapps_*.ps1
📄 CodeRabbit inference engine (CLAUDE.md)
PowerShell scenario tests belong in
tests/selfapps_<scenario>.ps1; wire new scenarios intotests/harness.ps1and the CI job steps.
Files:
tests/selfapps_gribapi_hook_probe.ps1
🪛 PSScriptAnalyzer (1.25.0)
tests/selfapps_gribapi_hook_probe.ps1
[warning] 172-172: Empty catch block is used. Please use Write-Error or throw statements in catch blocks.
(PSAvoidUsingEmptyCatchBlock)
[warning] 176-176: Empty catch block is used. Please use Write-Error or throw statements in catch blocks.
(PSAvoidUsingEmptyCatchBlock)
[warning] 177-177: Empty catch block is used. Please use Write-Error or throw statements in catch blocks.
(PSAvoidUsingEmptyCatchBlock)
[warning] 204-204: Empty catch block is used. Please use Write-Error or throw statements in catch blocks.
(PSAvoidUsingEmptyCatchBlock)
[warning] 208-208: Empty catch block is used. Please use Write-Error or throw statements in catch blocks.
(PSAvoidUsingEmptyCatchBlock)
[warning] 209-209: Empty catch block is used. Please use Write-Error or throw statements in catch blocks.
(PSAvoidUsingEmptyCatchBlock)
- Real bug: continue-on-error on the CI step does NOT make this probe non-gating -- batch-check.yml's separate "Verdict from NDJSON" + "Enforce NDJSON failures" steps scan raw pass values across ALL rows and fail the conda-full lane job on any pass=false, independent of which step emitted it. This diagnostic/exploratory probe must never gate, so every exit path now emits pass=true (skip=true or details.conclusive carries the real outcome) and exits 0 unconditionally. - Tighten "conclusive evidence": track buildLaunched/buildCompleted/ buildOutputCaptured and exeLaunched/exeCompleted/exeOutputCaptured explicitly per variant, so a timed-out process, launch exception, or unreadable output can no longer masquerade as a real (negative) finding via the old -1/empty-string defaults. observationComplete now requires all three EXE-side conditions before ranClean is trusted. - Tilde-prefix the temp log files (~env_create.log, ~build_$Variant.log) per repo convention. - Add Write-Warning to the two taskkill catch blocks (matching selfapps_layered_e2e.ps1's own established precedent); the remaining empty catches are the same "best-effort .Result read after a bounded Wait()" pattern already accepted in that file. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A4iE1BRSkUETwz237XeuTW
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
tests/selfapps_gribapi_hook_probe.ps1 (1)
132-141: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick winClean up the created environment before this skip.
When this guard runs,
conda createalready returned success. This branch exits before the post-create cleanup at Line 285.gribapi_probe_envcan remain on the runner.Run the same
conda env removecommand before this exit, or use one cleanup helper for every post-create exit.Suggested cleanup
if (-not (Test-Path -LiteralPath $envPython)) { + & cmd /c "call `"$condaBat`" env remove -y -n $envName >nul 2>&1" Write-NdjsonRow ([ordered]@{🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/selfapps_gribapi_hook_probe.ps1` around lines 132 - 141, The early exit at line 141 (the `exit 0` statement in the Test-Path guard block) bypasses the post-create cleanup logic that removes the conda environment at line 285, leaving gribapi_probe_env on the runner. Add the same conda environment removal command before this early `exit 0` to ensure the environment is cleaned up when the python.exe missing condition is detected, or create a shared cleanup helper function that runs before every post-create exit path in the script.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@tests/selfapps_gribapi_hook_probe.ps1`:
- Around line 260-261: Update the conclusive evidence gate around
$observationComplete, $ranClean, and $conclusive to define a successful build
observation requiring buildCompleted, buildOutputCaptured, and a successful
buildExit, then require that condition for both variants before reporting
conclusive evidence. Do not treat the executable's existence or launch results
as conclusive when the build timed out or failed.
- Around line 192-197: Update both timeout termination branches around the
taskkill.exe calls to capture $LASTEXITCODE immediately after invocation, then
call $proc.WaitForExit(5000) and treat a nonzero exit code or false result as
termination failure. Preserve the existing warning behavior while ensuring both
branches verify that the process actually exited.
- Around line 287-295: Change the PyInstaller build configuration from --onefile
to --onedir so DLLs are written to the filesystem under $distDir where they can
be detected by the scan. Update $exePath to point to the binary within the
variant subdirectory. Replace the $hiddenImportHelped calculation to measure DLL
bundling using $bundledDll instead of runtime cleanliness with $ranClean,
specifically using (-not $control.bundledDll) -and $experiment.bundledDll to
determine if the hidden-import helped the bundling outcome.
---
Outside diff comments:
In `@tests/selfapps_gribapi_hook_probe.ps1`:
- Around line 132-141: The early exit at line 141 (the `exit 0` statement in the
Test-Path guard block) bypasses the post-create cleanup logic that removes the
conda environment at line 285, leaving gribapi_probe_env on the runner. Add the
same conda environment removal command before this early `exit 0` to ensure the
environment is cleaned up when the python.exe missing condition is detected, or
create a shared cleanup helper function that runs before every post-create exit
path in the script.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 89e78f14-8138-401b-8cc5-2f4d8f17c9a4
📒 Files selected for processing (1)
tests/selfapps_gribapi_hook_probe.ps1
📜 Review details
🧰 Additional context used
📓 Path-based instructions (8)
**/*
📄 CodeRabbit inference engine (AGENTS.md)
The single-bootstrapper directive requires
run_setup.batto work when dropped next to the application without committed helper files; test-only helpers may live undertests/but cannot be required by the real flow.
Files:
tests/selfapps_gribapi_hook_probe.ps1
**/*.{ps1,psm1,psd1}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ps1,psm1,psd1}: Prepend TLS 1.2 configuration to every PowerShell 5.1Invoke-WebRequestcall and retain-UseBasicParsing.
Validate touched PowerShell files with PowerShell AST parsing; do not skip validation on Linux, and run modified scripts directly with realistic environment variables when practical.
Files:
tests/selfapps_gribapi_hook_probe.ps1
**/*.{bat,cmd,ps1,py,yml,yaml,json}
📄 CodeRabbit inference engine (AGENTS.md)
Run delimiter and repository-specific syntax regression checks using
tools/check_delimiters.py, respecting comments, escapes, and here-strings as applicable.
Files:
tests/selfapps_gribapi_hook_probe.ps1
**/*.{py,ps1,psm1,psd1,yml,yaml,json,bat,cmd}
📄 CodeRabbit inference engine (AGENTS.md)
Keep source text ASCII plain text and avoid non-ASCII punctuation.
Files:
tests/selfapps_gribapi_hook_probe.ps1
**/*.{bat,ps1}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{bat,ps1}: Use ASCII-only content; avoid emojis, curly quotes, em-dashes, and other non-ASCII characters because Windows batch and PowerShell parsing can break.
Keep .bat and .ps1 files in CRLF format; other files use LF, as controlled by .gitattributes.
Avoid EnableDelayedExpansion in batch scripts; if required, scope it tightly.
Files:
tests/selfapps_gribapi_hook_probe.ps1
**/*.{bat,ps1,py}
📄 CodeRabbit inference engine (CLAUDE.md)
Use tilde-prefixed temporary files such as
~setup.logand~bootstrap.status.jsonso they are easy to ignore and survive crashes.
Files:
tests/selfapps_gribapi_hook_probe.ps1
**/*.ps1
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.ps1: Use PowerShell-orand-andonly in valid conditional expressions; outside conditionals they may be parsed as parameter names.
Run a PowerShell parser sweep over modified PowerShell scripts before committing.
Files:
tests/selfapps_gribapi_hook_probe.ps1
tests/selfapps_*.ps1
📄 CodeRabbit inference engine (CLAUDE.md)
PowerShell scenario tests belong in
tests/selfapps_<scenario>.ps1; wire new scenarios intotests/harness.ps1and the CI job steps.
Files:
tests/selfapps_gribapi_hook_probe.ps1
🪛 PSScriptAnalyzer (1.25.0)
tests/selfapps_gribapi_hook_probe.ps1
[warning] 200-200: Empty catch block is used. Please use Write-Error or throw statements in catch blocks.
(PSAvoidUsingEmptyCatchBlock)
[warning] 201-201: Empty catch block is used. Please use Write-Error or throw statements in catch blocks.
(PSAvoidUsingEmptyCatchBlock)
[warning] 246-246: Empty catch block is used. Please use Write-Error or throw statements in catch blocks.
(PSAvoidUsingEmptyCatchBlock)
[warning] 247-247: Empty catch block is used. Please use Write-Error or throw statements in catch blocks.
(PSAvoidUsingEmptyCatchBlock)
- Critical: switch --onefile to --onedir. --onefile embeds all bundled support files (DLLs included) inside the compressed EXE, extracting them only to a runtime-only _MEIxxxxxx temp directory that never touches disk under the dist path -- the bundledDll filesystem scan could therefore NEVER detect a positive result under --onefile, regardless of whether the hidden-import actually worked. This was a structural flaw in the experiment itself, not a finding about the research question. --onedir writes bundled files under dist_$Variant\probe_$Variant\, where the scan can see them; $exePath updated to point into that subdirectory. - hiddenImportHelped now derived from bundledDll (a structural, build-output fact) instead of ranClean (a runtime-behavior fact that conflates "did it bundle" with "did it happen to run cleanly for any reason"); ranClean stays recorded per-variant as supplementary evidence. - Add buildObservationComplete (build launched, completed within budget, output captured, exited 0) as a prerequisite for observationComplete, so a build that timed out or left a partial EXE on disk before being killed can no longer be treated as conclusive evidence. - Clean up the scratch conda env before the python.exe-missing early exit, matching every other post-create exit path. - Verify taskkill.exe actually terminated the process (check $LASTEXITCODE + a bounded WaitForExit) instead of assuming success whenever the call itself didn't throw. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A4iE1BRSkUETwz237XeuTW
…s 2-6) Requirement 1's own CI experiment (PR #415, self.gribapi_hook_probe.hidden_import) confirmed --hidden-import=gribapi does not make hook-gribapi.py bundle eccodes.dll for a pygrib-only build, ruling out the free-lunch shortcut. This adds the actual repair loop: :dll_bundle_recover in run_setup.bat reacts to PyInstaller's own build-time "Library not found: could not resolve" warning (before the smoke run, skipping a guaranteed-failing verification cycle), mirrors :hidden_import_recover's bounded/tried-list shape, is gated to HP_ENV_MODE=conda for the actual --add-binary action, and carries the same HP_NUITKA_FALLBACK_USED guard. Built general (parses whatever DLL name PyInstaller names, not hardcoded to eccodes.dll) rather than narrow, resolving the open question in docs/open-questions.md. tools/dll_bundle_scan.py is the new scanning helper (HP_DLL_BUNDLE_SCAN payload), double-gated like its sibling: a named DLL must actually exist under the conda env's Library\bin (searched recursively) before anything is emitted. 24 unit tests in tests/test_dll_bundle_scan.py. tests/selfapps_layered_e2e.ps1 gains a 4th mechanism (mech4Pass) alongside the three it already proves for real -- pygrib's conda-forge build genuinely triggers this exact DLL-bundling gap, so this is the loop's own Requirement 4 regression test. Not yet confirmed in real CI (no Windows/conda available in this sandbox); flagged as such in CLAUDE.md's Item 24 entry pending a real cache-lane run. Also merges origin/main (PR #415) to pick up the gribapi probe test and its docs/agent-ndjson.md registration. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A4iE1BRSkUETwz237XeuTW
Real Windows-CI failure (all 8 lanes): tests/test_dll_bundle_scan.py wrote
fixture files via Path.write_text(), which translates \n to \r\n on Windows
but not Linux -- exact-string assertions in ReadTail passed locally and
failed on the real runner. Fixed by switching every fixture write to
Path.write_bytes(text.encode("utf-8")), which never translates.
Four genuine bugs caught by CodeRabbit review in the first-shipped
:dll_bundle_recover, all fixed:
- Detection itself was gated on HP_ENV_MODE=conda, defeating the PRD's own
"provider-agnostic detection" design. Restructured so a --detect-mode
scan runs first and unconditionally; the conda gate and the Nuitka guard
now each log "detected but can't repair" instead of staying silent.
- The tried-list was an unquoted batch variable expanded on the scan
command line -- a DLL basename containing a space or & would corrupt or
split it. Replaced with a file (~dll_bundle_tried.txt, appended via
type >>, never through %VAR%-expanded command text).
- main() stopped at the first candidate that failed the Library\bin
lookup instead of trying the next one. Fixed with a loop that skips
unresolvable candidates.
- The "bundling complete" log line could fire after a genuine rebuild
failure, since HP_DLL_ITER GEQ 1 is true on both the success and
failure paths. Fixed with an explicit HP_DLL_FAILED flag (the same bug
class already fixed once before for the warnfix-triggered rebuild).
tools/dll_bundle_scan.py gained --detect mode, read_tried_file(), and the
continue-past-unresolvable loop, with 10 new unit tests (34 total). PRD
status text updated from "expected negative" to the confirmed PR #415
result, and Requirement statuses reconciled to a single consistent state.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A4iE1BRSkUETwz237XeuTW
…tion) (#414) * Item 24: confirm hook-gribapi.py source, thaw PRD, doc/tooling research Reads pyinstaller-hooks-contrib's actual hook-gribapi.py source and confirms pygrib and gribapi are architecturally independent bindings to eccodes, so the "does an existing hook solve this for free" question is now assessed as likely-negative (still worth the cheap empirical CI test). Adds a Nuitka interaction finding/requirement (the new repair loop needs the same HP_NUITKA_FALLBACK_USED guard :hidden_import_recover already has; the elective optimized-build upsell doesn't "fall back" on failure, it just doesn't swap). Thaws the PRD from cold storage per owner instruction and moves active tracking back to CLAUDE.md's Item 24. Files an open question about extending the post-flight caveat panel with a DLL-specific hint once the repair loop's own detection signal exists. Also: corrects a stale AGENTS.md claim that PSGallery is blocked by proxy (disproved directly -- PSScriptAnalyzer installs and runs cleanly), and records research on both PSScriptAnalyzer and Blinter as sanity-sweep candidates -- neither adopted yet, both produce heavy false-positive noise against this repo's own deliberate conventions without a real per-rule audit first (documented as Cold Storage candidates with concrete thaw triggers). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A4iE1BRSkUETwz237XeuTW * Fix CodeRabbit review findings on Item 24 docs - Separate the PRD's proven fact (hook-gribapi.py won't auto-fire for a pygrib-only import) from the still-unverified expectation (forcing --hidden-import=gribapi likely won't help either) -- the two were conflated in the prior draft; Requirement 1's CI experiment is the actual decision point, not this section's reasoning. - Fix "Tier 9" -> "Requirement 9" naming (Tier A is reserved for the Nuitka AV-Safe Build Path). - Note conda-forge-only channel enforcement explicitly in Requirement 1's text (already correctly implemented in the experiment PR itself). - Rework the open-questions.md caveat-panel entry to distinguish detected-and-skipped (non-conda provider or Nuitka build, per Requirements 3/6) from attempted-and-failed/succeeded -- the original wording conflated "detection fired" with "repair was attempted." - Trim the Blinter/PSScriptAnalyzer cold-storage entries to reference AGENTS.md's own findings instead of duplicating them, per this repo's own "don't duplicate content across docs" convention. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A4iE1BRSkUETwz237XeuTW * Implement conda native-DLL bundling repair loop (Item 24, Requirements 2-6) Requirement 1's own CI experiment (PR #415, self.gribapi_hook_probe.hidden_import) confirmed --hidden-import=gribapi does not make hook-gribapi.py bundle eccodes.dll for a pygrib-only build, ruling out the free-lunch shortcut. This adds the actual repair loop: :dll_bundle_recover in run_setup.bat reacts to PyInstaller's own build-time "Library not found: could not resolve" warning (before the smoke run, skipping a guaranteed-failing verification cycle), mirrors :hidden_import_recover's bounded/tried-list shape, is gated to HP_ENV_MODE=conda for the actual --add-binary action, and carries the same HP_NUITKA_FALLBACK_USED guard. Built general (parses whatever DLL name PyInstaller names, not hardcoded to eccodes.dll) rather than narrow, resolving the open question in docs/open-questions.md. tools/dll_bundle_scan.py is the new scanning helper (HP_DLL_BUNDLE_SCAN payload), double-gated like its sibling: a named DLL must actually exist under the conda env's Library\bin (searched recursively) before anything is emitted. 24 unit tests in tests/test_dll_bundle_scan.py. tests/selfapps_layered_e2e.ps1 gains a 4th mechanism (mech4Pass) alongside the three it already proves for real -- pygrib's conda-forge build genuinely triggers this exact DLL-bundling gap, so this is the loop's own Requirement 4 regression test. Not yet confirmed in real CI (no Windows/conda available in this sandbox); flagged as such in CLAUDE.md's Item 24 entry pending a real cache-lane run. Also merges origin/main (PR #415) to pick up the gribapi probe test and its docs/agent-ndjson.md registration. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A4iE1BRSkUETwz237XeuTW * Update PRD status header to reflect implementation (Requirements 1-3/5/6) Fixes a markdown bug in the same edit: a multi-line "comment" using repeated ## prefixes would have rendered as three separate H2 headings. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A4iE1BRSkUETwz237XeuTW * Fix CI failure + address CodeRabbit review round on DLL bundling loop Real Windows-CI failure (all 8 lanes): tests/test_dll_bundle_scan.py wrote fixture files via Path.write_text(), which translates \n to \r\n on Windows but not Linux -- exact-string assertions in ReadTail passed locally and failed on the real runner. Fixed by switching every fixture write to Path.write_bytes(text.encode("utf-8")), which never translates. Four genuine bugs caught by CodeRabbit review in the first-shipped :dll_bundle_recover, all fixed: - Detection itself was gated on HP_ENV_MODE=conda, defeating the PRD's own "provider-agnostic detection" design. Restructured so a --detect-mode scan runs first and unconditionally; the conda gate and the Nuitka guard now each log "detected but can't repair" instead of staying silent. - The tried-list was an unquoted batch variable expanded on the scan command line -- a DLL basename containing a space or & would corrupt or split it. Replaced with a file (~dll_bundle_tried.txt, appended via type >>, never through %VAR%-expanded command text). - main() stopped at the first candidate that failed the Library\bin lookup instead of trying the next one. Fixed with a loop that skips unresolvable candidates. - The "bundling complete" log line could fire after a genuine rebuild failure, since HP_DLL_ITER GEQ 1 is true on both the success and failure paths. Fixed with an explicit HP_DLL_FAILED flag (the same bug class already fixed once before for the warnfix-triggered rebuild). tools/dll_bundle_scan.py gained --detect mode, read_tried_file(), and the continue-past-unresolvable loop, with 10 new unit tests (34 total). PRD status text updated from "expected negative" to the confirmed PR #415 result, and Requirement statuses reconciled to a single consistent state. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A4iE1BRSkUETwz237XeuTW * Fix CMD-injection risk in DLL bundle logging; add dedicated NDJSON row Two CodeRabbit findings on the DLL bundling repair loop's first review round: - HP_DLL_DETECTED/HP_NEXT_DLL/HP_NEXT_DLL_PATH (derived from PyInstaller's own build-log warning text) can legally contain &/|/</> on Windows, which :log's unquoted echo would reinterpret as live redirection/pipe operators. Fixed with display-only _SAFE sanitized variables used only in :log calls; every functional use of the raw value (tried-file byte-copy, quoted --add-binary argument) is untouched, so tried-list dedup matching cannot desync. - The loop's detected/skipped/repaired/unlocatable/failed outcomes previously reached only :log's console text, with no machine-readable record. Added a shared :emit_dll_bundle_row subroutine, called from all 6 outcome points, emitting NDJSON id self.dll_bundle.recover. The DLL name/provider/iteration are pulled inside the emitting PowerShell command via [Environment]::GetEnvironmentVariable(...) rather than %VAR% cmd.exe substitution, protecting cmd.exe's own command-line parsing the same way the _SAFE variables protect :log's echo. tests/harness.ps1 gains a static wiring check (batch.dll_bundle.ndjson) since this row is not observable in self.layered_e2e.chain's own artifact (that test's isolated sub-bootstrap leaves HP_NDJSON unset, matching the existing convention in selfapps_postexec_checkpoint.ps1). docs/agent-interconnect.md, docs/agent-ndjson.md, and CLAUDE.md's Item 24 entry updated accordingly. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A4iE1BRSkUETwz237XeuTW * Strip %/^ from DLL bundle log sanitization (call's second expansion pass) A third CodeRabbit review pass caught a real gap in the just-shipped _SAFE display-only sanitization: it stripped &/|/</> but not % or ^. call :log "... %VAR_SAFE% ..." triggers cmd.exe's own second expansion pass on its already-substituted command line (a well-established call behavior), so a raw % surviving into a _SAFE variable could still expand an unrelated environment variable into the log if the underlying DLL name happened to be shaped like %SOME_VAR% (regex-extracted from arbitrary PyInstaller warning text, so not restricted to real filename characters the way HP_NEXT_DLL_PATH otherwise would be). Extended all three _SAFE chains (HP_DLL_DETECTED_SAFE/HP_NEXT_DLL_SAFE/ HP_NEXT_DLL_PATH_SAFE) with two more substitutions each: % (doubled to %% in the search text, matching a literal percent sign) and ^ (no doubling needed). Documented as a standalone lesson in docs/agent-lessons-learned.md (the call-triggers-a-second-expansion-pass mechanism is a general cmd.exe hazard, not specific to this one loop) and in docs/agent-interconnect.md's DLL bundling section. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A4iE1BRSkUETwz237XeuTW * Fix cascade version-pin bug blocking self.layered_e2e.chain; add CI-verified percent-sanitizer test Real CI evidence (self.layered_e2e.chain, cache lane, 2026-08-07): chainPass came back false, but the failure was earlier than the DLL-bundling loop entirely -- mech1Pass:false, condaSelected:false, statusState:"venv_env". Root cause traced via the real ~setup.log/~layered_e2e_bootstrap.log artifacts: the runtime.txt write-back feature (REQ-004) pins the EXACT patch version uv resolved (e.g. python-3.14.7) the moment uv's own venv succeeds. When the REQ-009 cascade later re-enters targeting conda (pygrib still failing to build under uv), :after_env_mode_selection's re-derivation of PYSPEC reads that freshly-written runtime.txt and forwards python=3.14.7 verbatim to conda create -- but conda-forge's own python package release cadence is a separate index from CPython's/uv's and did not have that exact patch, producing PackagesNotFoundInChannelsError and a hard conda env create failure. The chain fell through embed -> venv, never reaching a real conda environment, so the DLL-bundling loop itself was never exercised in that run. Fixed with a new HP_PYSPEC_WRITEBACK flag, set at all 3 sites where write-back reassigns PYSPEC, marking that value as self-authored by whichever provider ran first rather than a genuine user requirement. :try_conda_create now drops the version pin (falls back to its existing unconstrained conda create branch) whenever HP_PYSPEC_WRITEBACK is set, at both the initial attempt and the transient retry -- a real pre-existing user pin (HP_RUNTIME_TXT_PREEXIST defined) is left untouched. selfapps_layered_e2e.ps1 gains a pinDropped visibility field to confirm the fix's own code path fires. Also settles a CodeRabbit/Blinter disagreement empirically: Blinter flagged the %VAR:%%=_% doubled-percent sanitization idiom (from the prior commit's CMD- injection fix) as a possible malformed string operation, since its static pattern matcher does not model the doubled-%% escape for a literal percent sign inside a :search=replace substitution. Rather than trust either static tool or further reasoning about undocumented cmd.exe parsing, tests/harness.ps1 gains a new gating check (batch.dll_bundle.pct_sanitizer) that runs the exact idiom against a real cmd.exe via a live fixture and asserts the observed output. Deferred to a new CLAUDE.md Active Backlog Item 25 (not fixed here, needs its own focused loop): a genuine but low-trigger-rate correctness gap where the DLL-bundle loop reports "repaired" instead of a distinct "exhausted" outcome when a 4th locatable DLL candidate is found after the 3-iteration cap is already hit. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A4iE1BRSkUETwz237XeuTW * Fix broken percent-sanitizer idiom; preserve user Python constraints on cascade The %VAR:%%=_% cmd.exe substitution shipped in the previous commit was confirmed broken by real Windows CI (silently produced an empty string instead of stripping a literal percent sign). Replaced the % and ^ stripping in the DLL-bundle loop's _SAFE sanitization with a PowerShell -replace call instead, and rewrote tests/harness.ps1's batch.dll_bundle.pct_sanitizer fixture to validate the real mechanism (plus a call-based second-expansion-pass leak check) instead of the abandoned idiom. Also adds HP_PYSPEC_ORIGINAL, snapshotted at each runtime.txt write-back site before PYSPEC is overwritten, so a REQ-009 provider cascade re-entry into conda preserves a genuine user-authored pyproject.toml/PEP 723 requires-python range instead of always dropping to no constraint. Fixes a related pre-existing bug found in the process: %PYSPEC% was used unquoted on both conda create command lines, so a PEP 440 range containing < or > would corrupt the command via cmd.exe redirection parsing. Corrects the docs that had described the broken %%=_% idiom as a standard cmd.exe pattern. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A4iE1BRSkUETwz237XeuTW * Correct Windows filename claim about & in agent-interconnect.md & is legal in NTFS filenames (only < > : " / \ | ? * are forbidden); the DLL-bundle sanitization strips it because it's a CMD transport hazard once the path reaches an unquoted :log echo, not because it's filesystem-illegal. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A4iE1BRSkUETwz237XeuTW * Register batch.dll_bundle.pct_sanitizer in the NDJSON row registry Real, non-stale finding from the advisory NDJSON registry cross-check CI job: this row (added earlier in the DLL-bundling percent-sanitizer fix) was emitted by tests/harness.ps1 but never registered in docs/agent-ndjson.md, violating the repo's own AGENT DIRECTIVE. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A4iE1BRSkUETwz237XeuTW * Add Item 26: ENVNAME sanitization loses readability on & in folder names Owner-suggested refinement (general guidance, not a bug report): the built EXE's filename (dist\%ENVNAME%.exe) already sanitizes & safely (blanket [^A-Za-z0-9_-] -> _), but collapsing it to an unreadable underscore instead of "and" loses meaning for a file a user might rename and email. Deferred as a far-term nice-to-have per its own framing -- no live safety issue. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A4iE1BRSkUETwz237XeuTW * Fix odd-percent-pairing bug in DLL-bundle _SAFE sanitization PowerShell calls Real CI failure across 5+ lanes (identical "ECHO is off." signature as the earlier %%=_% bug) confirmed the PowerShell -replace fix shipped in the prior commit was itself broken: a lone, unpaired % literal in `-replace '%','_'` sat on the same cmd.exe logical line as %LOG%/ %TEMP%. cmd.exe pairs % characters via a left-to-right scan of the whole line regardless of quoting, so the lone % paired with %LOG%'s own opening %, and everything between them (the real replace logic) was parsed as one bogus, undefined variable name -- silently deleted, since an undefined %VAR% collapses to empty text inside a batch file. Fixed by removing every literal % from the -Command text entirely: $pct = [char]37 builds the percent character inside PowerShell itself, leaving only the legitimate, correctly-paired %LOG%/%TEMP% reference on each line. Verified by counting % occurrences on each fully-joined logical line (exactly 2, the one intended pair, in every case). Static reasoning about cmd.exe's own %-pairing/substitution semantics has now been wrong three times in this one code path; the live-cmd.exe pct_sanitizer fixture is what caught rounds two and three. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A4iE1BRSkUETwz237XeuTW * Note first real CI evidence for exe_hint_rerun taskkill /T uncertainty test_hang_after_output_is_ALSO_killed_unlike_exe_smokerun genuinely timed out on 2/8 lanes (both non-gating) on a commit that touched neither the test nor tools/exe_hint_rerun.ps1 -- likely CI-runner timing contention, but recorded per this doc's existing "NOT CI-confirmed" note since it's the first real signal either way. Not held pending push -- concurrency.cancel-in-progress would cancel the in-progress gating-lane runs currently validating the actual fix. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A4iE1BRSkUETwz237XeuTW * Update exe_hint_rerun taskkill finding with second CI data point; file Item 27 uv lane's failure (AssertionError: 50.6s not less than 45s) is a DIFFERENT failure mode of the same UnconditionalKill test than contract-uv's hard 60s timeout -- two independent runner VMs showing correlated slowness in the same taskkill /F /T /PID path is enough to move this from a documentation note to a tracked Active Backlog item (Item 27), still unrelated to this PR's actual DLL-bundling work. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A4iE1BRSkUETwz237XeuTW * Reset HP_NEXT_DLL/HP_DLL_ITER before dll_bundle_recover's skip branches CodeRabbit finding, confirmed real: :dll_bundle_recover can run more than once per process during a REQ-009 cascade re-entry. HP_NEXT_DLL/ HP_DLL_ITER were previously reset only after the skipped_nuitka/ skipped_non_conda early exits, so a stale value left by an earlier call's successful repair leaked into a later call's skip row via emit_dll_bundle_row's "$dll = if (\$next) { \$next } else { \$detected }" fallback. Reset both immediately after the HP_DLL_DETECTED guard. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A4iE1BRSkUETwz237XeuTW * Bound taskkill/WaitForExit waits in exe_hint_rerun.ps1 (Item 27) The real (gating) lane hit test_exe_hint_rerun.py's UnconditionalKill test with the same slow-kill signature already seen twice on non-gating lanes (48.4s vs a 45s assertion). The post-kill sequence had two genuinely unbounded waits: the blocking `& taskkill.exe` call itself, and the trailing $p.WaitForExit() with no argument. Both are now bounded to 5000ms, with taskkill.exe launched via its own Process object instead of the `&` operator so it can be bounded at all. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A4iE1BRSkUETwz237XeuTW * Convert DLL-bundle % sanitizer to an emitted .ps1 helper The old inline `-Command` sanitization for HP_DLL_DETECTED_SAFE / HP_NEXT_DLL_SAFE / HP_NEXT_DLL_PATH_SAFE went through three separate real-CI-confirmed bugs, all rooted in cmd.exe's own %-pairing behavior on a line containing literal % text. Moved the logic into tools/dll_pct_sanitize.ps1, a real emitted file invoked via -File -- its body is never parsed by cmd.exe's tokenizer at all, eliminating the bug class structurally instead of patching around it again. Also documents the incident and the general rule (prefer -File over -Command whenever the body needs a literal %, not just embedded ") in docs/agent-lessons-learned.md and docs/agent-interconnect.md, and repoints the tests/harness.ps1 live-cmd.exe fixture at the real mechanism instead of a standalone reproduction of the retired one. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A4iE1BRSkUETwz237XeuTW * Note real-lane confirmation of the exe_hint_rerun taskkill fix commit f106f78's real lane came back clean (full pytest suite passed, job conclusion success), confirming the bounded-wait fix actually resolves the observed CI slowness for the lane that was gating-blocked. conda-full (the other gating lane) was still running as of this note. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A4iE1BRSkUETwz237XeuTW * Close Item 27 (both gating lanes confirmed) and align demo doc f106f78's real and conda-full lanes both came back conclusion:success, confirming the exe_hint_rerun taskkill fix resolved the CI slowness on both gating lanes. Item 27 moved from CLAUDE.md's Active Backlog to docs/agent-closed-backlog.md per the repo's own closure convention. Also brought docs/demo-bootstrapper-output.md back in line with the current state of the DLL-bundling repair loop and fixed two stale "Active Backlog item N" citations that should have pointed at docs/agent-closed-backlog.md instead, per that doc's own house style. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A4iE1BRSkUETwz237XeuTW * Fix HP_PY_DIR trailing-backslash argv corruption in DLL-bundle scan Root cause for self.layered_e2e.chain's mech4Pass staying false (and plausibly mech3Pass too): HP_PY_DIR (from %~dpI) always ends in one trailing backslash. Quoted immediately before another quoted argument at the ~dll_bundle_scan.py call site, that backslash escapes the closing quote instead of closing it (the same general CommandLineToArgvW hazard already documented for findstr.exe, here hitting python.exe's own argv parser) -- silently merging conda_env_dir with the tried-file argument into garbage, so locate_dll() always failed even though eccodes.dll is genuinely present in the real conda-forge package. Deterministic, not flaky -- this fired on every conda-provider run since the loop shipped. Fixed by doubling the trailing backslash (HP_PY_DIR_ARG) at the one call site that needed it. Verified via a faithful simulation of the Windows argv-parsing algorithm, since this hazard can't be reproduced via a real subprocess on Linux. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A4iE1BRSkUETwz237XeuTW * Close Item 24: DLL-bundling fix confirmed via real CI evidence Commit 45ec269's cache-lane run (31208498606) shows the native-DLL bundling loop genuinely working for the first time: eccodes.dll was located under the conda env's Library\bin and bundled into the EXE (mech4Pass confirmed true). Moved Item 24 to docs/agent-closed-backlog.md per the repo's own closure convention, and removed the "not yet confirmed" hedges from agent-interconnect.md and agent-lessons-learned.md. chainPass itself is still false on that run, but for a reason entirely outside Item 24's scope: now that the DLL-bundling gap no longer blocks progress, the EXE gets further and hits a new, deeper gap first -- pygrib's own extension needs numpy and packaging as hidden imports before colorama's own gap (what mech3 actually checks for) is ever reached. Filed as a new, separately-scoped Item 28 rather than reopening Item 24 or guessing at a fix. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A4iE1BRSkUETwz237XeuTW * docs: address CodeRabbit review findings on test count and pygrib version pin - Fix stale "34 tests" claim in agent-closed-backlog.md; the file has grown to 37 with the HpPyDirArgvQuoting class documented later in the same entry. - Pin the pygrib Windows-wheel-availability claim in demo-bootstrapper-output.md to a specific version+query-date instead of an unversioned "latest release", matching CodeRabbit's independently re-verified PyPI JSON API query. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A4iE1BRSkUETwz237XeuTW --------- Co-authored-by: Claude <noreply@anthropic.com>
Summary
Standalone, isolated CI experiment for CLAUDE.md Active Backlog Item 24 /
docs/prd-conda-native-dll-bundling.mdRequirement 1 (currently on the separate, not-yet-merged PR #414). Deliberately kept its own PR per explicit owner instruction, so this cheap "does the free lunch exist" question doesn't get entangled with the larger repair-loop implementation.tests/selfapps_gribapi_hook_probe.ps1: does not invokerun_setup.batat all. Creates its own scratch conda env (python pygrib eccodes python-eccodes pyinstaller pip --override-channels -c conda-forge), then builds a trivialimport pygribstub with PyInstaller twice — once as a control, once with--hidden-import gribapiadded — and records whethereccodes.dllends up bundled and whether each resulting EXE actually runs clean.conda-fulllane inbatch-check.yml, gated on the existingsteps.conda_avail.outputs.available == 'true'check (same gate the other 27 conda-full-only self-tests use),continue-on-error: true(exploratory — the three-way conda-forge solve is unproven, and the research finding this test is checking is expected to come back negative).docs/agent-ndjson.mdgets the new row registered (self.gribapi_hook_probe.hidden_import) with the full evidence/assertion writeup.Expected outcome, stated up front so a negative result isn't surprising: research in the PRD (reading
hook-gribapi.py's actual source, and confirmingpygrib/gribapiare architecturally independent bindings to the sameeccodesC library) suggests this will most likely come back negative —pygribnever importsgribapi, so PyInstaller's hook has no reason to fire. Still worth the real CI evidence per explicit owner instruction rather than relying on analysis alone.passin the emitted NDJSON row reflects whether the experiment ran to completion and produced conclusive evidence, not whether the hidden-import "worked" — the actual finding is recorded indetails.hiddenImportHelped.Test plan
python tools/check_delimiters.py tests/selfapps_gribapi_hook_probe.ps1— clean.pwsh -NoProfile -c "[...]::ParseFile(...)") — clean.actionlint -oneline .github/workflows/batch-check.yml— clean.tools/run_sanity_sweep.sh— all checks pass, 472 pytest passed / 2 skipped.conda-fulllane's new step is the actual test of this PR — will report back once it lands (est. slow, since it does its own conda solve + two PyInstaller builds inside an already-long lane).Depends on nothing else landing first; can merge independently of PR #414.
Generated by Claude Code